package com.ideaaedi.springcloud.jd.user.service.impl;

import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.ideaaedi.springcloud.jd.commonds.constant.BaseConstant;
import com.ideaaedi.springcloud.jd.commonds.entity.BaseCodeMsgEnum;
import com.ideaaedi.springcloud.jd.commonds.entity.Result;
import com.ideaaedi.springcloud.jd.commonds.entity.user.vo.req.LoginRefreshTokenReqVO;
import com.ideaaedi.springcloud.jd.commonds.entity.user.vo.req.LoginTokenCheckReqVO;
import com.ideaaedi.springcloud.jd.commonds.entity.user.vo.req.LoginTokenReqVO;
import com.ideaaedi.springcloud.jd.commonds.entity.user.vo.req.SysMenuListReqVO;
import com.ideaaedi.springcloud.jd.commonds.entity.user.vo.resp.LoginRefreshTokenRespVO;
import com.ideaaedi.springcloud.jd.commonds.entity.user.vo.resp.LoginTokenAkSkRespVO;
import com.ideaaedi.springcloud.jd.commonds.entity.user.vo.resp.LoginTokenCheckRespVO;
import com.ideaaedi.springcloud.jd.commonds.entity.user.vo.resp.LoginTokenRespVO;
import com.ideaaedi.springcloud.jd.commonds.entity.user.vo.resp.SysMenuListRespVO;
import com.ideaaedi.springcloud.jd.commonds.enums.user.UserTypeEnum;
import com.ideaaedi.springcloud.jd.commonds.exception.BaseException;
import com.ideaaedi.springcloud.jd.commonds.exception.RpcException;
import com.ideaaedi.springcloud.jd.commonds.ext.UsernameExt;
import com.ideaaedi.springcloud.jd.commonds.util.HttpUtil;
import com.ideaaedi.springcloud.jd.commonspring.mybatisplus.tenant.TenantDataScopeBO;
import com.ideaaedi.springcloud.jd.commonspring.mybatisplus.tenant.TenantProvider;
import com.ideaaedi.springcloud.jd.user.service.SysLoginService;
import com.ideaaedi.springcloud.jd.user.service.SysMenuService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.MDC;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.lang.NonNull;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;

/**
 * <p>
 * 系统-菜单表（资源组表） server
 * </p>
 *
 * @author <font size = "20" color = "#3CAA3C"><a href="https://gitee.com/JustryDeng">JustryDeng</a></font> <img src="https://gitee.com/JustryDeng/shared-files/raw/master/JustryDeng/avatar.jpg" />
 * @since 2021.0.1.A
 */
@Slf4j
@Service
public class SysLoginServiceImpl implements SysLoginService {
    
    /** 服务端口 */
    @Value("${server.port}")
    private String serverPort;
    
    @Resource
    SysMenuService sysMenuService;
    
    @Override
    public LoginTokenRespVO token(LoginTokenReqVO req) {
        return invokeOauth2CreateToken(UsernameExt.builder()
                .username(req.getAccountNo())
                .type(UsernameExt.UsernameType.ACCOUNT_NO_PASSWORD)
                .build(), req.getPassword(), TenantDataScopeBO.IGNORE_URD);
    }
    
    @Override
    public LoginRefreshTokenRespVO refreshToken(LoginRefreshTokenReqVO req) {
        Map<String, Object> map = new HashMap<>(8);
        map.put(BaseConstant.CLIENT_ID, BaseConstant.SYSTEM_USER_CLIENT_ID);
        map.put(BaseConstant.CLIENT_SECRET, BaseConstant.SYSTEM_USER_CLIENT_SECRET);
        map.put(BaseConstant.GRANT_TYPE, "refresh_token");
        map.put(BaseConstant.REFRESH_TOKEN, req.getRefreshToken());
        final String oauth2AuthServerTokenUrl =  String.format("localhost:%s/oauth/token", serverPort);
        Map<String, String> header = obtainTraceIdHeader();
        String oauth2RefreshTokenResp = null;
        JSONObject respMap;
        try {
            oauth2RefreshTokenResp = HttpUtil.postForm(oauth2AuthServerTokenUrl, map, header);
            log.info("oauth2RefreshTokenResp -> {}", oauth2RefreshTokenResp);
            respMap = JSON.parseObject(oauth2RefreshTokenResp);
            if (respMap.containsKey("error")) {
                throw new BaseException(oauth2RefreshTokenResp, BaseCodeMsgEnum.REFRESH_TOKEN_ERROR);
            }
        } catch (RpcException e) {
            throw new BaseException(e.getMessage(), e.getCodeMsgProvider());
        } catch (BaseException e) {
            throw e;
        } catch (Exception e) {
            throw new BaseException(oauth2RefreshTokenResp, BaseCodeMsgEnum.REFRESH_TOKEN_ERROR, e);
        }
        LoginRefreshTokenRespVO resp = new LoginRefreshTokenRespVO();
        resp.setToken(respMap.getString(BaseConstant.ACCESS_TOKEN));
        resp.setRefreshToken(respMap.getString(BaseConstant.REFRESH_TOKEN));
        // 这些信息也可单独起一个接口查询
        resp.setUserType(UserTypeEnum.valueOf(respMap.getString(BaseConstant.USER_TYPE)));
        resp.setUserId(respMap.getLong(BaseConstant.USER_ID));
        return resp;
    }
    
    @NonNull
    public LoginTokenCheckRespVO checkToken(LoginTokenCheckReqVO req) {
        String token = req.getToken();
        Map<String, Object> map = new HashMap<>(2);
        map.put(BaseConstant.TOKEN, token);
        String oauthTokenResp;
        final String oauth2AuthServerTokenUrl =  String.format("localhost:%s/oauth/check_token", serverPort);
        try {
            oauthTokenResp = HttpUtil.postForm(oauth2AuthServerTokenUrl, map, obtainTraceIdHeader());
        } catch (RpcException e) {
            throw e;
        } catch (Exception e) {
            throw new BaseException(e.getMessage(), BaseCodeMsgEnum.CHECK_TOKEN_FAIL, e);
        }
        log.info("invokeOauth2CheckToken oauthTokenResp -> {}", oauthTokenResp);
        JSONObject respMap = JSON.parseObject(oauthTokenResp);
        if (respMap.containsKey(BaseConstant.CODE) && respMap.containsKey(BaseConstant.MSG)) {
            throw new BaseException(oauthTokenResp, JSON.parseObject(oauthTokenResp, Result.class));
        }
        if (respMap.containsKey(BaseConstant.ERROR)) {
            throw new BaseException(oauthTokenResp, BaseCodeMsgEnum.CHECK_TOKEN_FAIL);
        }
        LoginTokenCheckRespVO resp = respMap.to(LoginTokenCheckRespVO.class);
        resp.setAccountNo(respMap.getString("user_name"));
        return resp;
    }
    
    /**
     * 调用oauth2获取token
     *
     * @param username 可扩展的username
     * @param password pwd
     * @param tenant 租户
     *
     * @return token信息
     */
    private LoginTokenRespVO invokeOauth2CreateToken(UsernameExt username, String password, String tenant) {
        Objects.requireNonNull(tenant, "tenant cannot be null.");
        Map<String, Object> map = new HashMap<>(8);
        map.put(BaseConstant.USERNAME, username.buildUsername());
        map.put(BaseConstant.PASSWORD, password);
        map.put(BaseConstant.CLIENT_ID, BaseConstant.SYSTEM_USER_CLIENT_ID);
        map.put(BaseConstant.CLIENT_SECRET, BaseConstant.SYSTEM_USER_CLIENT_SECRET);
        map.put(BaseConstant.GRANT_TYPE, "password");
        String oauthTokenResp;
        final String oauth2AuthServerTokenUrl =  String.format("localhost:%s/oauth/token", serverPort);
        try {
            oauthTokenResp = HttpUtil.postForm(oauth2AuthServerTokenUrl, map, obtainTraceIdHeader());
        } catch (RpcException e) {
            throw e;
        } catch (Exception e) {
            throw new BaseException(e.getMessage(), BaseCodeMsgEnum.LOGIN_FAIL, e);
        }
        log.info("invokeOauth2CreateToken oauthTokenResp -> {}", oauthTokenResp);
        JSONObject respMap = JSON.parseObject(oauthTokenResp);
        if (respMap.containsKey(BaseConstant.CODE) && respMap.containsKey(BaseConstant.MSG)) {
            throw new BaseException(oauthTokenResp, JSON.parseObject(oauthTokenResp, Result.class));
        }
        if (respMap.containsKey(BaseConstant.ERROR)) {
            throw new BaseException(oauthTokenResp, BaseCodeMsgEnum.USER_ACCOUNT_NO_OR_PASSWORD_ERROR);
        }
        return buildLoginTokenRespVO(respMap, tenant);
    }
    
    /**
     * 调用oauth2获取token
     * <p>
     * oauth2的client_credentials模式
     * </p>
     *
     * @param clientId 客户端id
     * @param clientSecret 客户端秘钥
     *
     * @return token信息
     */
    private LoginTokenAkSkRespVO invokeOauth2CreateTokenByClientMode(String clientId, String clientSecret) {
        Objects.requireNonNull(clientId, "clientId cannot be null.");
        Objects.requireNonNull(clientSecret, "clientSecret cannot be null.");
        Map<String, Object> map = new HashMap<>(8);
        map.put(BaseConstant.CLIENT_ID, clientId);
        map.put(BaseConstant.CLIENT_SECRET, clientSecret);
        map.put(BaseConstant.GRANT_TYPE, "client_credentials");
        String oauthTokenResp;
        final String oauth2AuthServerTokenUrl =  String.format("localhost:%s/oauth/token", serverPort);
        try {
            oauthTokenResp = HttpUtil.postForm(oauth2AuthServerTokenUrl, map, obtainTraceIdHeader());
        } catch (RpcException e) {
            throw e;
        } catch (Exception e) {
            throw new BaseException(e.getMessage(), BaseCodeMsgEnum.LOGIN_FAIL, e);
        }
        log.info("invokeOauth2CreateTokenByClientMode oauthTokenResp -> {}", oauthTokenResp);
        JSONObject respMap = JSON.parseObject(oauthTokenResp);
        if (respMap.containsKey(BaseConstant.CODE) && respMap.containsKey(BaseConstant.MSG)) {
            throw new BaseException(oauthTokenResp, JSON.parseObject(oauthTokenResp, Result.class));
        }
        if (respMap.containsKey(BaseConstant.ERROR)) {
            throw new BaseException(oauthTokenResp, BaseCodeMsgEnum.CLIENT_CREDENTIAL_LOGIN_ERROR);
        }
        LoginTokenAkSkRespVO resp = new LoginTokenAkSkRespVO();
        resp.setToken(respMap.getString(BaseConstant.ACCESS_TOKEN));
        resp.setRefreshToken(respMap.getString(BaseConstant.REFRESH_TOKEN));
        resp.setUserType(UserTypeEnum.valueOf(respMap.getString(BaseConstant.USER_TYPE)));
        return resp;
    }
    
    /**
     * 构建LoginTokenRespVO对象
     *
     * @param respMap
     *            oauth2认证结果
     * @param tenant
     *            租户信息
     * @return 登录响应信息
     */
    @NonNull
    private LoginTokenRespVO buildLoginTokenRespVO(JSONObject respMap, String tenant) {
        final Long userId = respMap.getLong(BaseConstant.USER_ID);
        // 用户可访问菜单
        SysMenuListReqVO menuReq = new SysMenuListReqVO();
        menuReq.setUserId(userId);
        menuReq.setPageSize(-1);
        final List<SysMenuListRespVO> menuList =
                TenantProvider.exec(() -> sysMenuService.list(menuReq).getDataList()
                        , tenant);
        
        LoginTokenRespVO resp = new LoginTokenRespVO();
        resp.setToken(respMap.getString(BaseConstant.ACCESS_TOKEN));
        resp.setRefreshToken(respMap.getString(BaseConstant.REFRESH_TOKEN));
        resp.setUserType(UserTypeEnum.valueOf(respMap.getString(BaseConstant.USER_TYPE)));
        resp.setUserId(userId);
        resp.setMenuList(menuList);
        return resp;
    }
    
    /**
     * 获取traceId请求头
     */
    @NonNull
    private Map<String, String> obtainTraceIdHeader() {
        Map<String, String> header = new HashMap<>(4);
        String traceId = MDC.get(BaseConstant.TRACE_ID);
        if (StringUtils.isNotBlank(traceId)) {
            header.put(BaseConstant.TRACE_ID, traceId);
        }
        return header;
    }
}
